use async_trait::async_trait;
use std::{collections::HashMap, fmt::Debug, pin::Pin, sync::Arc};
use zen_engine::{
    loader::{DecisionLoader, LoaderResponse, LoaderResult},
    model::DecisionContent,
};
pub mod sqlite_loader;
pub mod sys_loader;

use tokio::sync::RwLock;

#[async_trait]
pub trait RulesLoaderExt: Debug + Send + Sync {
    async fn load_rule(
        &self,
        key: &str,
    ) -> LoaderResult<DecisionContent>;
}

#[derive(Debug)]
pub struct RulesLoader {
    loader: Arc<dyn RulesLoaderExt>,
    memory_refs: Option<RwLock<HashMap<String, Arc<DecisionContent>>>>,
}

impl RulesLoader {
    pub fn new(loader: Arc<dyn RulesLoaderExt>) -> Self {
        Self { loader, memory_refs: Some(Default::default()) }
    }

    async fn load_rule<K>(
        &self,
        key: K,
    ) -> LoaderResponse
    where
        K: AsRef<str>,
    {
        if let Some(memory_refs) = &self.memory_refs {
            let mref = memory_refs.read().await;
            if let Some(decision_content) = mref.get(key.as_ref()) {
                return Ok(decision_content.clone());
            }
        }
        let result = self.loader.load_rule(key.as_ref()).await?;
        let ptr = Arc::new(result);
        if let Some(memory_refs) = &self.memory_refs {
            let mut mref = memory_refs.write().await;
            mref.insert(key.as_ref().to_string(), ptr.clone());
        }

        Ok(ptr)
    }
}

impl DecisionLoader for RulesLoader {
    fn load<'a>(
        &'a self,
        key: &'a str,
    ) -> Pin<Box<dyn Future<Output = LoaderResponse> + 'a + Send>> {
        Box::pin(self.load_rule(key))
    }
}
